package cc.owoo.godpen.network.http;

import cc.owoo.godpen.structure.ByteList;
import cc.owoo.godpen.util.N;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

/**
 * HTTP报文解析
 * Created by nimensei
 * 2022-03-01 上午 01:58
 **/
public class HttpAnalysis {
    /**
     * 输出报文
     *
     * @param output          输出流
     * @param abstractContent 报文内容
     * @param message         报文解析接口
     * @throws IOException 处理异常
     */
    static void write(OutputStream output, cc.owoo.godpen.network.http.AbstractContent abstractContent, MessageWrite message) throws IOException {
        StringBuilder string = new StringBuilder();
        string.append(message.stringifyAgreement()).append("\r\n");
        abstractContent.forHeader((key, value) -> {
            if (key.equals("transfer-encoding"))
                return;
            string.append(key).append(':').append(value).append("\r\n");
        });
        string.append("\r\n");
        output.write(string.toString().getBytes(abstractContent.encode));
        if (abstractContent.content != null) {
            output.write(abstractContent.content);
        }
        output.flush();
    }

    /**
     * 读取报文数据
     *
     * @param input 输入流
     */
    static void read(InputStream input, cc.owoo.godpen.network.http.AbstractContent abstractContent, MessageRead message) throws IOException {
        ByteList bytePool = new ByteList();
        int ln = 0;
        int c;
        while (true) {
            c = input.read();
            if (c == -1)
                break;
            if (c == '\r')
                continue;
            if (c == '\n') {
                if (++ln == 2)
                    break;
            } else
                ln = 0;
            bytePool.write(c);
        }
        analysisHeaderData(bytePool.toByteArray(), abstractContent, message);
        bytePool.reset();
        if (ln < 2)
            return;
        readBody(input, bytePool, abstractContent);
        abstractContent.content = bytePool.toByteArray();
        bytePool.reset();
    }

    /**
     * 读取内容
     *
     * @param input    输入流
     * @param bytePool 字节缓存池
     * @throws IOException 处理异常
     */
    static void readBody(InputStream input, ByteList bytePool, AbstractHeader header) throws IOException {
        int contentLength = N.toInt(header.getHeader("content-length"), -1);
        if (contentLength == 0)
            return;
        String transferEncoding = header.getHeader("transfer-encoding");
        // 分块读取
        if (transferEncoding != null && transferEncoding.equals("chunked")) {
            readChunked(input, bytePool);
            return;
        }
        // 固定长度读取
        if (contentLength > 0) {
            if (readBodyByte(input, bytePool, contentLength) != 0)
                throw new IOException("读取的数据小于Content-Length: " + contentLength);
        }
    }

    /**
     * 读取区块
     *
     * @param input    输入流
     * @param bytePool 字节缓存池
     * @throws IOException 处理异常
     */
    static void readChunked(InputStream input, ByteList bytePool) throws IOException {
        ByteList size = new ByteList(4);
        while (true) {
            if (!readLine(input, size))
                throw new IOException("区块数据header读取错误");
            String num = new String(size.toByteArray()).trim();
            size.reset();
            int len = Integer.parseInt(num, 16);
            if (len == 0)
                break;
            if (readBodyByte(input, bytePool, len) != 0)
                throw new IOException("区块数据body读取错误，len = " + len);
            if (!skipLine(input))
                throw new IOException("区块数据body读取错误，没有读取到\\r\\n");
        }
    }

    /**
     * 读取固定长度的内容
     *
     * @param input    输入流
     * @param bytePool byte池子
     * @param length   需要读取的长度
     * @return 剩余长度
     * @throws IOException 处理异常
     */
    static int readBodyByte(InputStream input, ByteList bytePool, int length) throws IOException {
        int c;
        while (length > 0) {
            if ((c = input.read()) == -1)
                break;
            bytePool.write(c);
            --length;
        }
        return length;
    }

    /**
     * 读取一行，不读取\r
     *
     * @param input 输入流
     * @return 是否读取完成
     */
    static boolean readLine(InputStream input, ByteList bytePool) throws IOException {
        int c;
        while ((c = input.read()) != -1) {
            if (c == '\n')
                return true;
            if (c == '\r')
                continue;
            bytePool.write(c);
        }
        return false;
    }

    /**
     * 跳过一行
     *
     * @param input 输入流
     * @return 是否读到'\n'
     */
    static boolean skipLine(InputStream input) throws IOException {
        int c;
        do {
            c = input.read();
            if (c == -1)
                return false;
        } while (c != '\n');
        return true;
    }

    /**
     * 解析请求头数据
     *
     * @param bytes 数据
     */
    static void analysisHeaderData(byte[] bytes, cc.owoo.godpen.network.http.AbstractContent header, MessageRead message) {
        int index = -1;
        while (++index < bytes.length)
            if (bytes[index] == '\n')
                break;
        analysisAgreement(bytes, index, message);
        int temp = index + 1;
        while (++index < bytes.length) {
            if (bytes[index] != '\n')
                continue;
            analysisHeader(bytes, temp, index, header);
            temp = index + 1;
        }
    }

    /**
     * 解析协议头
     *
     * @param bytes 数据
     * @param right 指针结束位置
     */
    static void analysisAgreement(byte[] bytes, int right, MessageRead message) {
        String string = analysisString(bytes, 0, right);
        if (string == null)
            return;
        message.analysisAgreement(string);
    }

    /**
     * 解析响应头
     *
     * @param bytes 数据
     * @param left  指针起始位置
     * @param right 指针结束位置
     */
    static void analysisHeader(byte[] bytes, int left, int right, AbstractContent content) {
        String string = analysisString(bytes, left, right);
        if (string == null)
            return;
        left = string.indexOf(':', 1);
        if (left == -1)
            return;
        String key = string.substring(0, left).trim();
        String value = string.substring(left + 1).trim();
        content.setHeader(key, value);
    }

    /**
     * 将byte数组转换成字符串
     *
     * @param bytes byte数组
     * @param left  指针开始位置
     * @param right 指针结束位置
     * @return 字符串
     */
    static String analysisString(byte[] bytes, int left, int right) {
        try {
            return new String(bytes, left, right - left, HttpUtil.encode);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }
}
